﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using PhiStore.Packages;
using PhiStore.Util;

namespace PhiStore.Security
{
    /// <summary>
    /// Methods used for encryption and decryption throughout the PhiStore
    /// </summary>
    public static class Encryption
    {
        /// <summary>
        /// Decrypts an Encrypted package
        /// </summary>
        /// <param name="package"></param>
        /// <param name="aes"></param>
        /// <param name="formatter"></param>
        /// <returns></returns>
        public static object Decrypt(Encrypted package, AesCryptoServiceProvider aes, BinaryFormatter formatter)
        {
            MemoryStream ms = new MemoryStream();
            ms.Write(package.Bytes, 0, package.Bytes.Length);
            ms.Position = 0L;
            CryptoStream crypto = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read);

            object result = formatter.Deserialize(crypto);

            ms.Close();
            crypto.Close();

            return result;
        }

        /// <summary>
        /// Encrypts any serializable object to an Encrypted package
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="aes"></param>
        /// <param name="formatter"></param>
        /// <returns></returns>
        public static Encrypted Encrypt(object obj, AesCryptoServiceProvider aes, BinaryFormatter formatter)
        {
            MemoryStream ms = new MemoryStream();
            CryptoStream crypto = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write);
            formatter.Serialize(crypto, obj);
            crypto.FlushFinalBlock();
            
            ms.Position = 0L;

            Encrypted result = new Encrypted(StreamUtil.Read(ms));
            ms.Close();
            crypto.Close();
            return result;
        }

        /// <summary>
        /// Encrypts any serializable object into a RSAEncrypted package
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="rsa"></param>
        /// <param name="formatter"></param>
        /// <returns></returns>
        public static RSAEncrypted RSAEncrypt(object obj, RSACryptoServiceProvider rsa, BinaryFormatter formatter)
        {
            MemoryStream memory = new MemoryStream();
            formatter.Serialize(memory, obj);
            memory.Position = 0;
            byte[] bytes = StreamUtil.Read(memory);
            memory.Close();

            List<byte> package = new List<byte>();

            //int BlockLength = (rsa.KeySize / 8) - 2 - 2 * SHA1.Create().ComputeHash( bytes ).Length;
            int BlockLength = (rsa.KeySize / 8) - 42; //42 is the meaning of life!
            int blocksCount = bytes.Length / BlockLength;
            for (int i = 0; i <= blocksCount; i++)
            {
                int bytesCount = bytes.Length - BlockLength * i;
                if (bytesCount > BlockLength)
                {
                    bytesCount = BlockLength;
                }
                byte[] block = new byte[bytesCount];
                Buffer.BlockCopy(bytes, BlockLength * i, block, 0, block.Length);

                byte[] encrypted = rsa.Encrypt(block, true);
                package.AddRange(encrypted);
            }

            return new RSAEncrypted(package.ToArray());
        }

        /// <summary>
        /// Decrypts a RSAEncrypted package
        /// </summary>
        /// <param name="package"></param>
        /// <param name="rsa"></param>
        /// <param name="formatter"></param>
        /// <returns></returns>
        public static object RSADecrypt(RSAEncrypted package, RSACryptoServiceProvider rsa, BinaryFormatter formatter)
        {
            MemoryStream memory = new MemoryStream();

            int keySize = rsa.KeySize / 8;
            int blocksCount = package.Bytes.Length / keySize;

            for (int i = 0; i < blocksCount; i++)
            {
                int bytesCount = package.Bytes.Length - keySize * i;
                if (bytesCount > keySize)
                {
                    bytesCount = keySize;
                }

                byte[] block = new byte[bytesCount];
                Buffer.BlockCopy(package.Bytes, keySize * i, block, 0, block.Length);
                byte[] decryptedData = rsa.Decrypt(block, true);
                StreamUtil.Copy(decryptedData, memory);
            }

            memory.Position = 0;

            object result = formatter.Deserialize(memory);
            
            memory.Close();
            return result;
        }

    }
}
